package org.example.common.advice;

import lombok.extern.slf4j.Slf4j;
import org.example.common.bean.CommonException;
import org.example.common.bean.ResponseBean;
import org.example.common.enums.ErrorEnum;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.*;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.util.NestedServletException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import javax.validation.UnexpectedTypeException;
import java.io.FileNotFoundException;
import java.nio.file.AccessDeniedException;

/**
 * 全局异常捕获.
 *
 * @author dingcan
 * @since 2019-07-08
 */
@RestControllerAdvice
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@Slf4j
public class GlobalExceptionHandler {
    private static final String EXCEPTION_MESSAGE = "异常";
    private static final String NETWORK_ERROR = "网络异常,请重试";
    
    @ExceptionHandler(value = Exception.class)
    public ResponseBean<String> defaultErrorHandler(Exception e) {
        log.error(EXCEPTION_MESSAGE, e);
        return ResponseBean.error(ErrorEnum.SERVER_ERROR.getErrorCode(), NETWORK_ERROR);
    }
    
    @ExceptionHandler(CommonException.class)
    public ResponseBean<String> handleCommonException(CommonException e) {
        log.warn(EXCEPTION_MESSAGE, e);
        return ResponseBean.error(e.getCode(), e.getMessage());
    }
    
    @ExceptionHandler(value = {IllegalArgumentException.class, IllegalStateException.class})
    public ResponseBean<String> handleAssertException(Exception e) {
        log.warn(EXCEPTION_MESSAGE, e);
        return ResponseBean.error(ErrorEnum.SERVER_ERROR.getErrorCode(), e.getCause().getMessage());
    }
    
    /**
     * 重复键异常处理方法。
     *
     * @param ex      异常对象。
     * @param request http请求。
     * @return 应答对象。
     */
    @ExceptionHandler(value = DuplicateKeyException.class)
    public ResponseBean<String> duplicateKeyExceptionHandle(DuplicateKeyException ex, HttpServletRequest request) {
        log.error("DuplicateKeyException exception from URL [" + request.getRequestURI() + "]", ex);
        return ResponseBean.error("数据保存失败，存在重复数据，请核对！");
    }
    
    
    @ExceptionHandler(value = DataAccessException.class)
    public ResponseBean<String> dataBaseAccessExceptionHandle(Exception ex, HttpServletRequest request) {
        log.error("DataBaseAccessException exception from URL [" + request.getRequestURI() + "]", ex);
        return ResponseBean.error(ex.getCause().getMessage());
    }
    
    @ExceptionHandler({
            BindException.class,                 // 请求参数 绑定到java bean上失败时抛出  关键词 : @Valid 、 Java bean 、表单(Content-Type: multipart/form-data)
            ConstraintViolationException.class,  // 普通参数(非 java bean)校验出错时抛出  关键词 : @Validated 、 非Java bean
            MethodArgumentNotValidException.class// 请求体   绑定到java bean上失败时抛出  关键词 : @Valid 、 @RequestBody、Java bean 、表单(Content-Type: application/json、Content-Type: application/xml)
    })
    public ResponseBean<String> handleBindException(Exception ex) {
        String errorMsg = "参数验证错误";
        BindingResult bindingResult = null;
        if (ex instanceof BindException) {
            BindException bindException = (BindException) ex;
            bindingResult = bindException.getBindingResult();
        }
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) ex;
            bindingResult = methodArgumentNotValidException.getBindingResult();
        }
        if (ex instanceof ConstraintViolationException) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) ex;
            errorMsg = constraintViolationException.getMessage();
        }
        if (bindingResult != null) {
            for (FieldError filedError : bindingResult.getFieldErrors()) {
//                errorMsg = filedError.getDefaultMessage() + ":[" + filedError.getField() + "]";
                errorMsg = filedError.getDefaultMessage();
            }
        }
        log.warn(EXCEPTION_MESSAGE, ex);
        return ResponseBean.error(ErrorEnum.SERVER_ERROR.getErrorCode(), errorMsg);
    }
    
    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class,
            UnexpectedTypeException.class,
            FileNotFoundException.class,
            HttpMessageConversionException.class,
            MultipartException.class,
            MaxUploadSizeExceededException.class,
            MethodArgumentTypeMismatchException.class,
            MissingRequestHeaderException.class,
            HttpMediaTypeException.class,
            AccessDeniedException.class,
            NestedServletException.class
    })
    public ResponseBean<String> handleServletException(Exception e) {
        log.warn(EXCEPTION_MESSAGE, e);
        return ResponseBean.error(ErrorEnum.SERVER_ERROR.getErrorCode(), e.getCause().getMessage());
    }
}
